import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { WechatPayEntity } from "./wechatPay.entity";
import { Repository } from "typeorm";
import * as crypto from "crypto";
import axios from "axios";
import { signData } from "../../utils/hash";
import { hostUrl } from "../../utils/hostUrl";

@Injectable()
export class WechatPayPayment {
  constructor(
    @InjectRepository(WechatPayEntity)
    private readonly wechatPayEntity: Repository<WechatPayEntity>
  ) {}
  /**
   * Obtain payment parameter
   */
  private get wechatPayConfig() {
    return this.wechatPayEntity.findOne({
      where: {
        id: 1
      }
    })
  }

  /**
   * Generate payment link
   * @param query
   */
  public async generateUrl(query: any): Promise<string> {
    const { total, title, order } = query // order amount, order title, order number
    const { appid, mchid } = await this.wechatPayConfig
    const data = {
      appid: appid,
      mchid: mchid,
      description: title,
      attach: "pay",
      notify_url: hostUrl("/api/wechatPay/notify"),
      out_trade_no: order,
      amount: {
        total: parseFloat(total) * 100,
        currency: "CNY"
      },
      scene_info: {
        payer_client_ip: "127.0.0.1"
      }
    }
    const authorization = await this.generateAuthorization(data)
    const result = await axios.post(
      "https://api.mch.weixin.qq.com/v3/pay/transactions/native",
      JSON.stringify(data),
      {
        headers: {
          Authorization: `${authorization}`,
          "Content-Type": "application/json",
          Accept: "application/json",
          "User-Agent": "PostmanRuntime/7.32.3"
        }
      }
    )
    return result.data["code_url"]
  }

  /**
   * Generate Authorization
   * @param data
   */
  private async generateAuthorization(data: object): Promise<string> {
    const body = JSON.stringify(data)
    const nonce = this.generateRandomString()
    const timestamp = Math.floor(Date.now() / 1000)
    const message = `POST\n/v3/pay/transactions/native\n${timestamp}\n${nonce}\n${body}\n`
    const sign = await this.sign(message)
    const { mchid, serial_number } = await this.wechatPayConfig
    return (
      "WECHATPAY2-SHA256-RSA2048" +
      " " +
      `mchid="${mchid}",nonce_str="${nonce}",timestamp="${timestamp}",serial_no="${serial_number}",signature="${sign}"`
    )
  }

  /**
   * Generate 32-bit random strings
   */
  private generateRandomString(): string {
    const characters =
      "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
    let randomString = ""
    for (let i = 0; i < 32; i++) {
      const randomIndex = Math.floor(Math.random() * characters.length)
      randomString += characters[randomIndex]
    }
    return randomString
  }

  /**
   * Signature
   * @param message
   */
  private async sign(message: string): Promise<string> {
    const { private_key } = await this.wechatPayConfig
    return signData(message, private_key)
  }

  /**
   * Aes-256-gcm decrypt
   * @param ciphertext
   * @param nonceStr
   * @param associatedData
   */
  public async decrypt(
    ciphertext: string,
    nonceStr: Buffer,
    associatedData: Buffer
  ): Promise<any> {
    const { api_key_v3 } = await this.wechatPayConfig
    const ciphertextBuffer = Buffer.from(ciphertext, "base64")
    const ctext = ciphertextBuffer.subarray(0, -16)
    const authTag = ciphertextBuffer.subarray(-16)
    const decipher = crypto.createDecipheriv(
      "aes-256-gcm",
      api_key_v3,
      nonceStr
    )
    decipher.setAuthTag(authTag)
    decipher.setAAD(associatedData)
    let decrypted = decipher.update(ctext)
    decrypted = Buffer.concat([decrypted, Buffer.from(decipher.final())])
    return JSON.parse(decrypted.toString("utf8"))
  }
}
